<?php
declare(strict_types=1);

namespace app\shop\service;

use app\admin\model\shop\order\Export;
use app\admin\model\shop\order\Order;
use app\shop\enums\order\OrderExportStatus;
use app\shop\enums\order\OrderPayWay;
use app\shop\enums\order\OrderStatus;
use app\shop\exception\FailException;
use app\shop\job\OrderExportJob;
use app\shop\library\datetime\TimeHelper;
use app\shop\library\excel\PhpSpreadSheet;
use app\shop\library\ShopHelper;
use ba\Exception;
use think\facade\Queue;
use think\helper\Arr;
use Throwable;

class OrderExportService
{
    private Export $model;

    public function __construct()
    {
        $this->model = new Export();
    }

    /**
     * 可导出的字段
     * @return array[]
     */
    public function orderExportField(): array
    {
        return [
            ['column' => 'id', 'name' => '订单ID', 'width' => '15'],
            ['column' => 'order_no', 'name' => '订单号', 'width' => 'auto'],
            ['column' => 'out_trade_no', 'name' => '商户单号', 'width' => 'auto'],
            ['column' => 'goods_info', 'name' => '商品信息', 'width' => '40'],
            ['column' => 'status_text', 'name' => '订单状态', 'width' => 'auto'],
            ['column' => 'pay_way_text', 'name' => '支付方式', 'width' => 'auto'],
            ['column' => 'total_price', 'name' => '订单应付金额', 'width' => 'auto', 'format' => 'currency'],
            ['column' => 'freight', 'name' => '运费金额', 'width' => 'auto', 'format' => 'currency'],
            ['column' => 'total_discount', 'name' => '订单优惠金额', 'width' => 'auto', 'format' => 'currency'],
            ['column' => 'pay_price', 'name' => '实际支付金额', 'width' => 'auto', 'format' => 'currency'],
            ['column' => 'user_name', 'name' => '下单用户', 'width' => 'auto'],
            ['column' => 'buyer_remark', 'name' => '用户备注', 'width' => '30'],
            ['column' => 'buyer', 'name' => '收货人', 'width' => 'auto'],
            ['column' => 'buyer_phone', 'name' => '收货人联系方式', 'width' => 'auto'],
            ['column' => 'buyer_address', 'name' => '收货人联系地址', 'width' => '35'],
            ['column' => 'create_time', 'name' => '下单时间', 'width' => 'auto'],
            ['column' => 'pay_time', 'name' => '支付时间', 'width' => 'auto'],
            ['column' => 'delivery_time', 'name' => '发货时间', 'width' => 'auto'],
            ['column' => 'receive_time', 'name' => '收货时间', 'width' => 'auto'],
            ['column' => 'evaluate_time', 'name' => '评价时间', 'width' => 'auto'],
            ['column' => 'cancel_time', 'name' => '取消时间', 'width' => 'auto'],
        ];
    }

    /**
     * 格式化数据
     * @param       $order
     * @param array $headers
     * @return array
     */
    public function formatOrderData($order, array $headers): array
    {
        $empty   = '';
        $dataMap = [
            'id'             => $order->id,
            'order_no'       => $order->order_no,
            'out_trade_no'   => $order->out_trade_no,
            'goods_info'     => $this->formatGoodsInfo($order),
            'status_text'    => OrderStatus::getAllDescriptions()[$order->status] ?? $empty,
            'pay_way_text'   => OrderPayWay::getAllDescriptions()[$order->pay_way] ?? $empty,
            'total_price'    => $order->total_price,
            'freight'        => $order->freight,
            'total_discount' => $order->total_discount,
            'pay_price'      => $order->pay_price,
            'user_name'      => $order->user?->nickname,
            'buyer_remark'   => $order->remark,
            'buyer'          => $order->address?->name,
            'buyer_phone'    => $order->address?->phone,
            'buyer_address'  => $order->address?->full_address,
            'create_time'    => $order->create_time,
            'pay_time'       => $order->pay_time ? TimeHelper::format($order->pay_time) : $empty,
            'delivery_time'  => $order->delivery_time ? TimeHelper::format($order->delivery_time) : $empty,
            'receive_time'   => $order->receive_time ? TimeHelper::format($order->receive_time) : $empty,
            'evaluate_time'  => $order->evaluate_time ? TimeHelper::format($order->evaluate_time) : $empty,
            'cancel_time'    => $order->cancel_time ? TimeHelper::format($order->cancel_time) : $empty,
        ];

        $data = [];
        foreach ($headers as $header) {
            if (Arr::has($dataMap, $header)) {
                $data[$header] = $dataMap[$header];
            }
        }

        return $data;
    }

    /**
     * 格式化商品信息
     * @param $order
     * @return string
     */
    private function formatGoodsInfo($order): string
    {
        if (!$order->detail) {
            return '';
        }

        $info = array_map(function ($item) {
            return sprintf(
                "商品名称：%s，商品规格：%s，购买数量：%d，总价：%.2f",
                $item['goods_name'],
                $item['spec'],
                $item['num'],
                $item['total_price']
            );
        }, $order->detail->toArray());

        return trim(implode("\n", $info));
    }

    /**
     * 添加订单导出队列任务
     * @param int $taskId
     * @return void
     * @throws FailException
     */
    public function addExportOrderJob(int $taskId): void
    {
        $data = ['id' => $taskId];

        try {
            Queue::push(OrderExportJob::class, $data, OrderExportJob::$queue);
        } catch (Throwable $e) {
            throw new FailException('订单导出任务创建失败', null, $e, $data, true);
        }
    }

    /**
     * 导出订单操作
     * @param int $recordId 导出记录id
     * @throws Exception
     */
    public function exportOrder(int $recordId): string
    {
        ini_set('memory_limit', '256M');

        $record = $this->model->where('id', $recordId)->findOrEmpty();
        if ($record->isEmpty()) {
            throw new Exception('导出记录不存在');
        }

        [
            'where'         => $where,
            'alias'         => $alias,
            'limit'         => $limit,
            'order'         => $order,
            'withJoinTable' => $withJoinTable,
            'withJoinType'  => $withJoinType,
        ] = $record->query_params;

        $orderModel = new Order();

        $exportFields = ShopHelper::json_to_array($record->export_fields);

        $headColumns = array_column($exportFields, 'column');

        $excelObj = new PhpSpreadSheet();
        $excelObj->setExportFileName($record->name)
            ->setExportHeader($exportFields)
            ->setExportMaxRow($record->max_row_num ?: 10000);

        $record->status = OrderExportStatus::RUNNING;
        $record->save();

        $counter = 0;
        try {
            $orderModel
                ->alias($alias)
                ->withJoin($withJoinTable, $withJoinType)
                ->with([
                    'detail'     => function ($query) {
                        $query->field(['order_id', 'goods_name', 'spec', 'num', 'total_price']);
                    }, 'user'    => function ($query) {
                        $query->field(['id', 'nickname']);
                    }, 'address' => function ($query) {
                        $query->field(['order_id', 'name', 'phone', 'full_address']);
                    }
                ])
                ->where($where)
                ->order($order)
                ->chunk(2000, function ($orders) use (
                    $record, $excelObj, &$exportFiles, $headColumns, $limit, &$counter
                ) {
                    $orderData = [];
                    foreach ($orders as $order) {
                        $orderData[] = $this->formatOrderData($order, $headColumns);
                        $counter++;
                        if ($counter >= $limit) {
                            $excelObj->setExportData($orderData);
                            return false;
                        }
                    }
                    return $excelObj->setExportData($orderData);
                }, 'Order.id', 'desc');

            $exportFilePath = $excelObj->exportToServer();

            $this->model->where('id', $record->id)->update([
                'status'             => OrderExportStatus::SUCCESS,
                'export_file_path'   => $exportFilePath,
                'export_finish_time' => time(),
                'error_info'         => ''
            ]);
        } catch (Throwable $e) {
            throw new Exception(
                $e->getMessage(),
                500,
                [
                    'line' => $e->getLine(),
                    'file' => $e->getFile()
                ]
            );
        }

        return $exportFilePath;
    }
}